﻿using System;
using System.Collections.Generic;
using System.Linq;
using Boxi.Extensions;
//using Boxi.ViewModel;

namespace Boxi.Model
{
    public struct GameModel
    {
        #region Enumerations
        public enum DifficultyType
        {
            Easy = 0,
            Moderate = 1,
            Difficult = 2
        }
        public enum ThemeType
        {
            Arcade, 
            Traditional
        }
        #endregion
        #region Properties

        public Boolean IsDark
        {
            get
            {
                return _isDark;
            }
        }
        public Boolean _isDark;

        public Byte Columns
        {
            get
            {
                return _columns;
            }
        }
        private Byte _columns;

        public Byte Rows
        {
            get
            {
                return _rows;
            }
        }
        private Byte _rows;
        #endregion
        #region Constructors
        public GameModel(Byte columns, Byte rows, Boolean isDark)
        {
            _columns = columns;
            _rows = rows;
            _isDark = isDark;
        }
        #endregion
        #region Methods
        /// <summary>
        /// Warning!!! Does not make copy
        /// </summary>
        /// <param name="playerID"></param>
        /// <param name="moveOptions"></param>
        /// <param name="moveHistories"></param>
        /// <param name="game"></param>
        /// <returns></returns>
        public static MoveModel[] GetMovesConnectedThatWouldScore(MoveOptionList moveOptions, MoveHistoryList moveHistories, SByte playerID, GameModel game, ref Byte score, Byte moveID)
        {
            if (moveID == 0)
                throw new Exception("moveID can not be  0");

            var movesThatWouldScore = new List<MoveModel>();

            Byte originalScore = score;
            score += (Byte)moveOptions.GetPointsThatWillScore(moveID, game).Length;
            if (moveOptions.GetByID(moveID).MoveMadeOrder == 0)
            {
                moveOptions.SetMove(moveHistories, moveID, playerID, game);
                movesThatWouldScore.Add(moveOptions[moveID - 1]);
            }

            if (score > originalScore)
            {
                var availableRelatedMoves = moveOptions.GetOpenOffensiveMoves(game).Where(m => moveOptions.WillRelatedMoveScore(m, moveID, game)).ToArray();
                if (availableRelatedMoves.Length >= 1)
                    GetMovesConnectedThatWouldScore(moveOptions, moveHistories, playerID, game, ref score, availableRelatedMoves[0].MoveID).ToList().ForEach(m => movesThatWouldScore.Add(m));
            }
            return movesThatWouldScore.ToArray();
        }
        /// <summary>
        /// Warning!!! Does not make copy
        /// </summary>
        /// <param name="playerID"></param>
        /// <param name="moveOptions"></param>
        /// <param name="moveHistories"></param>
        /// <param name="game"></param>
        /// <returns></returns>
        public static SByte PredictWhoWillScoreMore(MoveOptionList moveOptions, MoveHistoryList moveHistories, SByte[] players, SByte playerID, GameModel game, Int32 moves)
        {
            var currentPlayerID = playerID;

            var playerScoreChange = new Byte[players.Length];

            for (Byte i = 0; i < moves; i++)
            {
                Byte score = 0;
                SetAllScoringMoves(moveOptions, moveHistories, playerID, game, ref score);
                playerScoreChange[currentPlayerID] += score;
                MoveModel[] movesThatDoNotLetNextPlayerScore = moveOptions.GetOpenDefensiveMoves(game);
                if (movesThatDoNotLetNextPlayerScore.Length > 0)
                {
                    moveOptions.SetMove(moveHistories, movesThatDoNotLetNextPlayerScore.GetRandom().MoveID, currentPlayerID, game);
                }
                else
                {
                    MoveModel[] openMoves = moveOptions.GetOpenMoves();
                    if (openMoves.Length > 0)
                    {
                        moveOptions.SetMove(moveHistories, openMoves.GetRandom().MoveID, currentPlayerID, game);
                    }
                    else
                    {
                        break;
                    }
                }
                currentPlayerID = players.GetNextPlayer(currentPlayerID);
            }
            SByte highestScorePlayer = 0;
            Byte highestScore = 0;
            for (Byte i = 0; i < playerScoreChange.Length; i++)
            {
                if (playerScoreChange[i] > highestScore || (playerScoreChange[i] == highestScore && players[i] == playerID))
                {
                    highestScore = playerScoreChange[i];
                    highestScorePlayer = players[i];
                }
            }

            if (highestScorePlayer == 0)
                throw new Exception("highestScorePlayer IS 0");

            return highestScorePlayer;
        }
        /// <summary>
        ///  Makes Copy
        /// </summary>
        /// <param name="playerID"></param>
        /// <param name="moveOptions"></param>
        /// <param name="moveHistories"></param>
        /// <param name="game"></param>
        /// <returns></returns>
        public static MoveModel GetMostDefensiveMove(MoveOptionList moveOptions, SByte playerID, MoveHistoryList moveHistories, GameModel game)
        {
            //Copy MoveData
            var bestMove = default(MoveModel);
            Byte lowestScore = 255;
            var defensiveMoves = moveOptions.GetOpenBadMoves(game);
            for (Byte i = 0; i < defensiveMoves.Length; i++)
            {
                MoveHistoryList moveHistoriesCopy = moveHistories.Copy();
                MoveOptionList moveOptionCopy = moveOptions.Copy();
                Byte score = 0;
                moveOptionCopy.SetMove(moveHistoriesCopy, defensiveMoves[i].MoveID, playerID, game);
                SetAllScoringMoves(moveOptionCopy, moveHistoriesCopy, playerID, game, ref score);
                if (score < lowestScore)
                {
                    lowestScore = score;
                    bestMove = defensiveMoves[i];
                }
            }
            return bestMove;
        }
        /// <summary>
        /// Warning!!! Does not make copy
        /// </summary>
        /// <param name="playerID"></param>
        /// <param name="moveOptions"></param>
        /// <param name="moveHistories"></param>
        /// <param name="game"></param>
        /// <returns></returns>
        public static void SetAllScoringMoves(MoveOptionList moveOptions, MoveHistoryList moveHistories, SByte playerID, GameModel game, ref Byte score)
        {
            var scored = false;
            var availableMoves = moveOptions.GetOpenOffensiveMoves(game);
            for (Byte i = 0; i < availableMoves.Length; i++)
            {
                score += (Byte)moveOptions.GetPointsThatWillScore(availableMoves[i].MoveID, game).Length;
                moveOptions.SetMove(moveHistories, availableMoves[i].MoveID, playerID, game);
                scored = true;
            }
            if (scored)
                SetAllScoringMoves(moveOptions, moveHistories, playerID, game, ref score);
        }
        /// <summary>
        /// Makes Copy
        /// </summary>
        /// <param name="moveID"></param>
        /// <param name="playerID"></param>
        /// <param name="moveOptions"></param>
        /// <param name="moveHistories"></param>
        /// <param name="game"></param>
        /// <returns></returns>
        public static MoveModel GetMostOffensiveMove(MoveOptionList moveOptions, MoveHistoryList moveHistories, SByte[] playerIDs, SByte playerID, GameModel game)
        {
            var mostOffensiveMove = default(MoveModel);
            var offensiveMoves = moveOptions.GetOpenOffensiveMoves(game);
            if (offensiveMoves.Length == 0)
                return mostOffensiveMove;

            //if (offensiveMoves.Length > 1)
            //{
            //    foreach (var offensiveMove in offensiveMoves)
            //    {
            //        Byte offensiveScore = 0;
            //        var offensiveMoveOptionsCopy = moveOptions.Copy();
            //        var offensiveMoveHistoriesCopy = moveHistories.Copy();
            //        var offensiveMoveConnectedThatWouldScore = GetMovesConnectedThatWouldScore(offensiveMoveOptionsCopy, offensiveMoveHistoriesCopy, playerID, game, ref offensiveScore, offensiveMove.MoveID);
            //        var isMoveInConnected = false;
            //        foreach (var connectedMove in offensiveMoveConnectedThatWouldScore)
            //        {
            //            if (offensiveMove.MoveID == connectedMove.MoveID)
            //            {
            //                isMoveInConnected = true;
            //                break;
            //            }
            //        }
            //        if (!isMoveInConnected)
            //        {
            //            foreach (var offMove in offensiveMoves)
            //            {
            //                if (!((offensiveMoveConnectedThatWouldScore.Length == 3 && offensiveScore == 4) || (offensiveMoveConnectedThatWouldScore.Length == 2 && offensiveScore == 2)))
            //                    return offMove;
            //            }
            //        }
            //    }
            //}
            var isThrowOff = false;
            List<MoveModel> movesTested = new List<MoveModel>();
            if (offensiveMoves.Length > 0)
            {
                foreach (var offensiveMove in offensiveMoves)
                {
                    if (movesTested.Contains(offensiveMove))
                        continue;

                    movesTested.Add(offensiveMove);
                    Byte score = 0;
                    var isConnectedToOtherOffensiveMove = false;
                    var moveOptionsCopy = moveOptions.Copy();
                    var moveHistoriesCopy = moveHistories.Copy();
                    var movesConnectedThatWouldScore = GetMovesConnectedThatWouldScore(moveOptionsCopy,
                                                                                       moveHistoriesCopy, playerID, game,
                                                                                       ref score,
                                                                                       offensiveMove.MoveID);
                    foreach (var move in offensiveMoves)
                    {
                        foreach (var movesConnected in movesConnectedThatWouldScore)
                        {
                            if (move.MoveID == movesConnected.MoveID && !movesTested.Contains(move))
                            {
                                movesTested.Add(move);
                                isConnectedToOtherOffensiveMove = true;
                            }
                        }
                    }

                    if ((movesConnectedThatWouldScore.Length == 3 && score == 4) ||
                        (movesConnectedThatWouldScore.Length == 2 && score == 2))
                    {
                        Byte scoreNext = 0;
                        var move = GetMostDefensiveMove(moveOptionsCopy, playerID, moveHistoriesCopy, game);
                        if (move.MoveID == 0)
                        {
                            mostOffensiveMove = offensiveMove;
                            continue;
                        }
                        else
                            moveOptionsCopy.SetMove(moveHistoriesCopy, move.MoveID, playerID, game);

                        var offensiveMovesNext = moveOptionsCopy.GetOpenOffensiveMoves(game);

                        List<MoveModel> movesConnectedThatWouldScoreNext = new List<MoveModel>();
                        foreach (var offensiveMoveNext in offensiveMovesNext)
                        {
                            GetMovesConnectedThatWouldScore(moveOptionsCopy, moveHistoriesCopy, playerID, game,
                                                            ref scoreNext, offensiveMoveNext.MoveID).ToList().ForEach(
                                                                m => movesConnectedThatWouldScoreNext.Add(m));
                        }


                        if (scoreNext > score)
                        {
                            var throwOffMoves = movesConnectedThatWouldScore.Where(
                             m =>
                             moveOptions.GetByID(m.MoveID).MoveMadeOrder == 0 &&
                             moveOptions.IsPointForNextPlayerNotCurrent(m, game))
                             .ToArray();
#if (DEBUG)
                            if (throwOffMoves.Length > 1)
                                throw new Exception("throwOffMoves Length > 1");
#endif
                            if (throwOffMoves.Length == 0)
                            {
                                mostOffensiveMove = offensiveMove;
                            }
                            else
                            {
                                Byte scoreAfterNext = 0;
                                var moveAfter = GetMostDefensiveMove(moveOptionsCopy, playerID, moveHistoriesCopy, game);
                                if (moveAfter.MoveID == 0)
                                {
                                    mostOffensiveMove = throwOffMoves.GetRandom();
                                }
                                else
                                {
                                    moveOptionsCopy.SetMove(moveHistoriesCopy, moveAfter.MoveID, playerID, game);
                                    var offensiveMovesAfterNext = moveOptionsCopy.GetOpenOffensiveMoves(game);
                                    var movesConnectedThatWouldScoreAfterNext = GetMovesConnectedThatWouldScore(
                                        moveOptionsCopy.Copy(), moveHistoriesCopy.Copy(), playerID, game,
                                        ref scoreAfterNext,
                                        offensiveMovesAfterNext.GetRandom().MoveID);

                                    mostOffensiveMove = (scoreAfterNext <= 3)
                                                            ? offensiveMove
                                                            : throwOffMoves.GetRandom();
                                }

                                if (throwOffMoves[0].MoveID == mostOffensiveMove.MoveID)
                                {
                                    isThrowOff = true;
                                    foreach (var scoringMove in moveOptions.GetOpenOffensiveMoves(game))
                                    {
                                        Boolean scoreOutOfSequence = true;
                                        foreach (var connectedScoringMove in movesConnectedThatWouldScore)
                                        {
                                            if (connectedScoringMove.MoveID == scoringMove.MoveID)
                                                scoreOutOfSequence = false;
                                        }
                                        if (scoreOutOfSequence)
                                        {
                                            mostOffensiveMove = scoringMove;
                                            isThrowOff = false;
                                            break;
                                        }

                                    }
                                }
                            }
                        }
                        else
                        {
                            mostOffensiveMove = offensiveMove;
                        }
                    }
                    else
                    {
                        mostOffensiveMove = offensiveMove;
                        if (isConnectedToOtherOffensiveMove)
                            continue;
                    }
                    if (!isThrowOff)
                        break;
                }
            }
            return mostOffensiveMove;
        }
        public static MoveModel GetComputerMove(MoveOptionList moveOptions, MoveHistoryList moveHistories, SByte[] playerIDs, SByte currentPlayerID, DifficultyType difficulty, GameModel game)
        {
            if (difficulty == DifficultyType.Easy)
            {
                var movesThatScore = moveOptions.GetOpenOffensiveMoves(game);
                if (movesThatScore.Length > 0)
                    return movesThatScore.GetRandom();

                var movesThatDoNotLetNextPlayerScore = moveOptions.GetOpenDefensiveMoves(game);
                if (movesThatDoNotLetNextPlayerScore.Length > 0)
                    return movesThatDoNotLetNextPlayerScore.GetRandom();

                return moveOptions.GetOpenMoves().GetRandom();
            }
            else if (difficulty == DifficultyType.Moderate)
            {
                var movesThatScore = moveOptions.GetOpenOffensiveMoves(game);
                if (movesThatScore.Length > 0)
                    return movesThatScore.GetRandom();


                var movesThatDoNotLetNextPlayerScore = moveOptions.GetOpenDefensiveMoves(game);
                if (movesThatDoNotLetNextPlayerScore.Length > 0)
                    return movesThatDoNotLetNextPlayerScore.GetRandom();

                var move = GetMostDefensiveMove(moveOptions, currentPlayerID, moveHistories, game);
                if (move.MoveID != 0)
                    return move;

                return moveOptions.GetOpenMoves().GetRandom();
            }
            else
            {
                var mostOffensiveMove = GetMostOffensiveMove(moveOptions, moveHistories, playerIDs, currentPlayerID, game);
                if (mostOffensiveMove.MoveID != 0)
                    return mostOffensiveMove;

                var movesThatDoNotLetNextPlayerScore = moveOptions.GetOpenDefensiveMoves(game);
                if (movesThatDoNotLetNextPlayerScore.Length > 0)
                    return movesThatDoNotLetNextPlayerScore.GetRandom();

                var move = GetMostDefensiveMove(moveOptions, currentPlayerID, moveHistories, game);
                if (move.MoveID != 0)
                    return move;

                return moveOptions.GetOpenMoves().GetRandom();
            }

        
        }
        public override String ToString()
        {
            return String.Format("{0},{1}", Columns, Rows);
        }
        #endregion 
    }
}
